#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include <poll.h>
#include <err.h>

#include <xcb/xcb.h>
#include <xcb/xcb_event.h>

typedef struct window {
    xcb_connection_t	*con;
    xcb_screen_t		*scr;
    xcb_window_t		 win;
} win_t;

static xcb_gc_t get_font_gc(win_t *win, const char *font_name)
{
    xcb_font_t font = xcb_generate_id (win->con);
    xcb_void_cookie_t font_cookie = xcb_open_font(win->con, font,
            strlen(font_name), font_name);
    /* create graphics context */
    xcb_gcontext_t  gc  = xcb_generate_id(win->con);
    uint32_t mask		= XCB_GC_FOREGROUND | XCB_GC_BACKGROUND | XCB_GC_FONT;
    uint32_t values[3]	= { win->scr->black_pixel, win->scr->white_pixel, font };

    xcb_void_cookie_t gc_cookie = xcb_create_gc(win->con, gc, win->win, mask, values );
    /* close font */
    font_cookie = xcb_close_font(win->con, font);

    return gc;
}

void draw_text(win_t *win, int16_t x, int16_t y, const char *text)
{
    /* see '/usr/share/fonts/X11/misc/fonts.alias' */
    /* fixed 7x13 8x16 kana14 8x13bold heb8x13 */
    xcb_gcontext_t gc = get_font_gc(win, "fixed");

    /* draw the text */
    xcb_void_cookie_t text_cookie = xcb_image_text_8(win->con,
            strlen(text), win->win, gc, x, y, text);

    /* free the gc */
    xcb_void_cookie_t gc_cookie = xcb_free_gc(win->con, gc);
}

void check_cookie()
{
}

void handle_event(xcb_generic_event_t *evt)
{
    switch (XCB_EVENT_RESPONSE_TYPE(evt)) {
        case XCB_MAP_REQUEST:
            printf("map request\n");
            break;
        case XCB_DESTROY_NOTIFY:
            printf("destroy notify\n");
            break;
        case XCB_UNMAP_NOTIFY:
            printf("unmap notify\n");
            break;
        case XCB_CLIENT_MESSAGE:
            printf("client message\n");
            break;
        case XCB_CONFIGURE_REQUEST:
            printf("config request\n");
            break;
        case XCB_CONFIGURE_NOTIFY:
            printf("config notify\n");
            break;
        case XCB_PROPERTY_NOTIFY:
            printf("property notify\n");
            break;
        case XCB_ENTER_NOTIFY:
            printf("enter notify\n");
            break;
        case XCB_MOTION_NOTIFY:
            printf("motion notify\n");
            break;
        case XCB_BUTTON_PRESS:
            printf("button\n");
            break;
        case XCB_FOCUS_IN:
            printf("focus in\n");
            break;
        case XCB_MAPPING_NOTIFY:
            printf("mapping notify\n");
            break;
        case 0:
            // process_error(evt);
            printf("error\n");
            break;
        default:
            printf("default\n");
            break;
    }
}

bool check_connection(xcb_connection_t *connection)
{
    int xerr;
    if ((xerr = xcb_connection_has_error(connection)) != 0) {
        warn("The server closed the connection: ");
        switch (xerr) {
            case XCB_CONN_ERROR:
                warn("socket, pipe or stream error.\n");
                break;
            case XCB_CONN_CLOSED_EXT_NOTSUPPORTED:
                warn("unsupported extension.\n");
                break;
            case XCB_CONN_CLOSED_MEM_INSUFFICIENT:
                warn("not enough memory.\n");
                break;
            case XCB_CONN_CLOSED_REQ_LEN_EXCEED:
                warn("request length exceeded.\n");
                break;
            case XCB_CONN_CLOSED_PARSE_ERR:
                warn("can't parse display string.\n");
                break;
            case XCB_CONN_CLOSED_INVALID_SCREEN:
                warn("invalid screen.\n");
                break;
            case XCB_CONN_CLOSED_FDPASSING_FAILED:
                warn("failed to pass FD.\n");
                break;
            default:
                warn("unknown error.\n");
                break;
        }
        return false;
    } else {
        return true;
    }
}

int main()
{
    int default_screen;
    win_t window;
    win_t *win = &window;

    win->con = xcb_connect(NULL, &default_screen);

    //xcb_connection_has_error();
    if(!check_connection(win->con)) {
        exit(EXIT_FAILURE);
    }
    // get screen
    win->scr = xcb_setup_roots_iterator(xcb_get_setup(win->con)).data;
    //  ask for a graphic context ID
    win->win = xcb_generate_id (win->con);

    uint32_t mask = XCB_CW_BACK_PIXEL		 | \
                    XCB_CW_BORDER_PIXEL		 |
                    XCB_CW_OVERRIDE_REDIRECT | 
                    XCB_CW_EVENT_MASK;

    // values[0] = screen->white_pixel;
    uint32_t value_list[] = {
        0xFFFFFF,
        0x0088FF,
        false, // no window
        XCB_EVENT_MASK_EXPOSURE |
            XCB_EVENT_MASK_BUTTON_PRESS |
            XCB_EVENT_MASK_BUTTON_RELEASE
    };

    /* connection depth, window Id, parent window,
       x, y, width, height, border_width, class, visual, masks*/
    xcb_create_window(win->con, XCB_COPY_FROM_PARENT, win->win, win->scr->root,
            100, 100, 300, 100, 1,
            XCB_WINDOW_CLASS_INPUT_OUTPUT, win->scr->root_visual, mask, value_list);

    xcb_map_window(win->con, win->win);


    int connection_fd = xcb_get_file_descriptor(win->con);
    bool running = true;
    struct pollfd fds[1];
    fds[0].fd = connection_fd;
    fds[0].events = POLLIN;

    xcb_generic_event_t *event;

    while(running) {
        xcb_flush(win->con);

        // TODO: polling ...
        if(poll(fds, 1, -1) > 0) {
            if(fds[0].revents & POLLIN) {
                while ((event = xcb_poll_for_event(win->con)) != NULL) {
                    draw_text(win, 5, 40, "helloworld");
                    handle_event(event);
                    // free(event);
                }
            }
        }

        if (!check_connection(win->con)) {
            running = false;
        }
    }
    // TODO: cleanup();
}

// NOTE: xcb_poly_fill_rectangle
#if 0
values[0] = 200;
values[1] = 200;
xcb_configure_window (connection, window, \
        XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, values);
#endif
